/*
 * Copyright (C) Michael Martin & Antonio Carrozzini (TBJD)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.tbjd.client.guis.gantt;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.tbjd.client.beans.Resource;
import org.tbjd.client.beans.Task;
import org.tbjd.client.borderDetectors.GanttBorderNotifier;
import org.tbjd.client.constants.GanttConstants;
import org.tbjd.client.constants.NotificationsConstants;
import org.tbjd.client.constants.TimeConstants;
import org.tbjd.client.constants.ZoomConstants;
import org.tbjd.client.dndControllers.gantt.GanttItemDragController;
import org.tbjd.client.dndControllers.gantt.taskItem.GanttTaskItemDurationDragController;
import org.tbjd.client.dndControllers.gantt.taskItem.GanttTaskItemDurationDropController;
import org.tbjd.client.dndControllers.gantt.taskItem.GanttTaskItemProgressDragController;
import org.tbjd.client.dndControllers.resourcePicker.ResourcePickerDragController;
import org.tbjd.client.dndControllers.resourcePicker.ResourcePickerDropController;
import org.tbjd.client.exceptions.NoUpperDecisionmakerFoundException;
import org.tbjd.client.exceptions.NoUpperMediatorFoundException;
import org.tbjd.client.exceptions.TBJDException;
import org.tbjd.client.guis.gantt.items.AbstractGanttItem;
import org.tbjd.client.guis.gantt.items.GanttPhaseItem;
import org.tbjd.client.guis.gantt.items.GanttTaskItem;
import org.tbjd.client.interfaces.BorderListener;
import org.tbjd.client.interfaces.BorderNotifier;
import org.tbjd.client.interfaces.BorderTrigger;
import org.tbjd.client.interfaces.Builder;
import org.tbjd.client.interfaces.Decisionmaker;
import org.tbjd.client.interfaces.Displayer;
import org.tbjd.client.interfaces.HasDragController;
import org.tbjd.client.interfaces.HasMediator;
import org.tbjd.client.interfaces.Observer;
import org.tbjd.client.interfaces.Sender;
import org.tbjd.client.mediators.AbstractMediator;

import com.allen_sauer.gwt.dnd.client.DragController;
import com.allen_sauer.gwt.dnd.client.PickupDragController;
import com.allen_sauer.gwt.dnd.client.drop.GridConstrainedDropController;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * <p>
 * The GanttItemContainerGui is the panel that displays {@link org.tbjd.client.guis.gantt.items.AbstractGanttItem AbstractGanttItem}
 * </p>
 * 
 * @author Michael Martin
 * @author Antonio Carrozzini
 * @version 1.0
 * 
 */
public class GanttItemContainerGui extends Composite implements BorderListener, BorderTrigger, Builder, Displayer, HasMediator, Decisionmaker,
		Observer, HasDragController, Sender {

	private List<Task> tasks = null;
	private List<Resource> resources = null;

	private AbsolutePanel container = null;
	private static GanttItemContainerGui instance = null;
	private AbstractMediator mediator = null;
	private List<AbstractGanttItem> lstGanttItems = null;
	private GanttItemDragController ganttItemDragController = null;
	private GridConstrainedDropController dropController = null;
	private AbstractGanttItem draggedItem = null;
	private Date draggedItemRefBeginDate = null;
	private Map<Integer, Object> map = null;
	/**
	 * The offset used to position each DraggableGanttItem when dragging over a border of the GanttGui
	 */
	private int offsetMultiple = 0;
	private BorderNotifier borderNotifier = null;

	private GanttTaskItemDurationDragController ganttTaskItemDurationDragController = null;
	private GanttTaskItemDurationDropController ganttTaskItemDurationDropController = null;
	private PickupDragController ganttTaskItemProgressDragController = null;
	private ResourcePickerDragController resourcePickerDragController = null;
	private List<ResourcePickerDropController> lstResourcePickerDropController = new ArrayList<ResourcePickerDropController>();

	private GanttItemContainerGui() {
		build();
		initWidget(this.container);
		addBorderNotifier();
	}

	public void build() {
		this.container = new AbsolutePanel();
		this.container.setWidth(ZoomConstants.TIMELINE_COLUMN_WIDTH * ZoomConstants.FACTOR * GanttConstants.TIMELINE_COLUMN_NUMBER + "px");
		GanttBorderNotifier.getInstance().addBorderListener(this);
	}

	static public GanttItemContainerGui getInstance() {
		if (instance == null) {
			instance = new GanttItemContainerGui();
		}
		return instance;
	}

	public Widget getBuildedWidget() {
		return GanttItemContainerGui.instance;
	}

	public void display() {
		this.container.clear();

		// set the height of the container which is equal to all AbstractGanttItems multiplied by a factor
		this.container.setHeight(this.lstGanttItems.size()
				* ((GanttConstants.DRAGGABLEGANTTITEM_HEIGHT + GanttConstants.SLIDERPANEL_HEIGHT) + GanttConstants.DRAGGABLEGANTTITEM_SPACE) + "px");

		GanttTaskItem firstGanttTaskItem = null;

		if (this.lstGanttItems != null) {
			
			// set xCoordinate for every GanttTaskItem
			for (AbstractGanttItem eachItem : this.lstGanttItems) {
				if (eachItem instanceof GanttTaskItem) {
					if (firstGanttTaskItem == null){
						firstGanttTaskItem = (GanttTaskItem) eachItem;
					}
					computeXCoordinate((GanttTaskItem)firstGanttTaskItem, (GanttTaskItem)eachItem);
					
					// add it to the GanttItemContainerGui
					this.container.add((Widget) eachItem, eachItem.getXCoordinate(), eachItem.getYCoordinate());
					
					// make it draggable
					this.ganttItemDragController.makeDraggable(eachItem, eachItem.getHandle());

					GanttTaskItem ganttTaskItem = (GanttTaskItem) eachItem;

					this.ganttTaskItemProgressDragController = new GanttTaskItemProgressDragController(ganttTaskItem.getProgressPanel(), true);
					this.ganttTaskItemProgressDragController.makeDraggable(ganttTaskItem.getProgressHandle());

					this.ganttTaskItemDurationDragController = new GanttTaskItemDurationDragController(ganttTaskItem.getDurationPanel(), true);
					this.ganttTaskItemDurationDragController.makeDraggable(ganttTaskItem.getDurationHandle());
					this.ganttTaskItemDurationDropController = new GanttTaskItemDurationDropController(ganttTaskItem.getDurationPanel());
					this.ganttTaskItemDurationDragController.registerDropController(ganttTaskItemDurationDropController);

					// define each drop target with the resourcePickerDropController
					this.lstResourcePickerDropController.add(new ResourcePickerDropController(ganttTaskItem));

					// register each resourceDropController with the resourcePickerDragController
					this.resourcePickerDragController.registerDropController(this.lstResourcePickerDropController
							.get(this.lstResourcePickerDropController.size() - 1));
					
				}
			}

			// set xCoordinate for every GanttPhaseItem
			for (AbstractGanttItem eachItem : this.lstGanttItems) {
				if (eachItem instanceof GanttPhaseItem) {
					if (eachItem.getLstChildItem() != null && eachItem.getLstChildItem().size() > 0) {
						eachItem.setXCoordinate(eachItem.getLstChildItem().get(0).getXCoordinate());
					} else {
						eachItem.setXCoordinate(0);
					}
					
					// add it to the GanttItemContainerGui
					this.container.add((Widget) eachItem, eachItem.getXCoordinate(), eachItem.getYCoordinate());
					
					// make it draggable
					this.ganttItemDragController.makeDraggable(eachItem, eachItem.getHandle());
				}
			}
		}

	}

	public void setMediator(AbstractMediator mediator) {
		this.mediator = mediator;
	}

	public void decide(Object action) {
		if (((Map<?, ?>) action).containsKey(NotificationsConstants.DRAGGING_START)) {
			this.draggedItem = (AbstractGanttItem) ((Map<Integer, ?>) action).get(NotificationsConstants.DRAGGING_START);
			this.draggedItemRefBeginDate = ((Task) this.draggedItem.getBean()).getBeginDate();
		}

		if (((Map<?, ?>) action).containsKey(NotificationsConstants.DRAGGING_DRAGGABLEGANTTITEM)) {
			handle(action);
		}

		if (((Map<?, ?>) action).containsKey(NotificationsConstants.DRAGGING_STOP)) {
			// change beans list
			for (Task eachTask : this.tasks) {
				if (eachTask.getId() == ((Task) this.draggedItem.getBean()).getId()) {
					eachTask.setBeginDate(((Task) this.draggedItem.getBean()).getBeginDate());
				}
			}

			// notify mediator with new list
			this.map = new HashMap<Integer, Object>();
			this.map.put(NotificationsConstants.TASKS, this.tasks);
			this.map.put(NotificationsConstants.TASK_UPDATED, this.draggedItem.getBean());
			try {
				this.mediator.decide(map);
			} catch (TBJDException e) {
				e.printStackTrace();
			}

			// reset temp variables
			this.draggedItem = null;
			this.offsetMultiple = 0;
		}

		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.TASK_DURATION_DRAGGING)) {
			int mouseX = (Integer) ((Map<Integer, ?>) action).get(NotificationsConstants.TASK_DURATION_DRAGGING)
					+ GanttConstants.SCROLL_HORIZONTAL_OFFSET;
			// find the scrollpanel that holds the GanttGui
			Widget child = this;
			while (child instanceof ScrollPanel == false) {
				child = child.getParent();
			}
			this.borderNotifier.mouseEvent(child, null, mouseX, 0);
		}

		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.DROP_ADD_RESOURCEONTASK_resource)) {
			System.out.println("GanttItemContainerGui.decide() DROP_ADD_RESOURCEONTASK_resource");

			// add a new GanttResourceItem to the GanttTaskItem
			handle(action);

			// then change the map
			GanttTaskItem taskItem = (GanttTaskItem) ((Map<Integer, ?>) action).get(NotificationsConstants.DROP_ADD_RESOURCEONTASK_ganttTaskItem);
			Resource resource = (Resource) ((Map<Integer, ?>) action).get(NotificationsConstants.DROP_ADD_RESOURCEONTASK_resource);
			this.map = new HashMap<Integer, Object>();
			this.map.put(NotificationsConstants.TASKS, this.tasks);
			this.map.put(NotificationsConstants.RESOURCES, this.resources);
			this.map.put(NotificationsConstants.DROP_ADD_RESOURCEONTASK_task, taskItem.getBean());
			this.map.put(NotificationsConstants.DROP_ADD_RESOURCEONTASK_resource, resource);

			try {
				delegate(action);

			} catch (NoUpperDecisionmakerFoundException e) {
				e.printStackTrace();

			} catch (NoUpperMediatorFoundException e) {
				e.printStackTrace();
			}
		}

		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.DROP_REMOVE_RESOURCEFROMTASK_resource)) {
			System.out.println("GanttItemContainerGui.decide() DROP_REMOVE_RESOURCEFROMTASK_resource");
			Resource resource = (Resource) ((Map<Integer, ?>) action).get(NotificationsConstants.DROP_REMOVE_RESOURCEFROMTASK_resource);
			Task task = (Task) ((Map<Integer, ?>) action).get(NotificationsConstants.DROP_REMOVE_RESOURCEFROMTASK_task);

			// update list of resources
			if (resource != null) {
				removeResource(resource);
			}

			// then change the map
			this.map = new HashMap<Integer, Object>();
			this.map.put(NotificationsConstants.TASKS, this.tasks);
			this.map.put(NotificationsConstants.RESOURCES, this.resources);
			this.map.put(NotificationsConstants.DROP_REMOVE_RESOURCEFROMTASK_task, task);
			this.map.put(NotificationsConstants.DROP_REMOVE_RESOURCEFROMTASK_resource, resource);

			try {
				delegate(action);

			} catch (NoUpperDecisionmakerFoundException e) {
				e.printStackTrace();

			} catch (NoUpperMediatorFoundException e) {
				e.printStackTrace();
			}
		}
	}

	public void delegate(Object action) throws NoUpperDecisionmakerFoundException, NoUpperMediatorFoundException {
		System.out.println("GanttItemContainerGui.delegate()");
		try {
			this.mediator.decide(action);
		} catch (TBJDException e) {
			e.printStackTrace();
		}
	}

	public void handle(Object action) {
		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.DRAGGING_DRAGGABLEGANTTITEM)) {
			updateDateOfDraggedItem(action);
		}

		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.DROP_ADD_RESOURCEONTASK_resource)) {
			System.out.println("GanttItemContainerGui.handle() DROP_ADD_RESOURCEONTASK_resource");

			// add a new GanttResourceItem to the GanttTaskItem
			GanttTaskItem taskItem = (GanttTaskItem) ((Map<Integer, ?>) action).get(NotificationsConstants.DROP_ADD_RESOURCEONTASK_ganttTaskItem);
			Resource resource = (Resource) ((Map<Integer, ?>) action).get(NotificationsConstants.DROP_ADD_RESOURCEONTASK_resource);
			addResourceItem(taskItem, resource);
		}
	}

	/**
	 * <p>
	 * Returns the position in pixels on the 'x' axis for the given targetItem relative to the given refItem
	 * </p>
	 * 
	 * @param refItem
	 *            represents an AbstractGanttItem which already has an xCoordinate set or is the first AbstractGanttItem of the list (thus its
	 *            xCoordinate = 0)
	 * @param targetItem
	 *            the item for which the xCoordinate is returned
	 * @return the 'x' position in pixels for the given targetItem
	 */
	private void computeXCoordinate(GanttTaskItem refItem, GanttTaskItem targetItem) {
		Long beginDateInMilis = 0l;
		
		// if the targetItem is not the first AbstractGanttItem
		if (!targetItem.equals(refItem)){
			beginDateInMilis = ((Task) targetItem.getBean()).getBeginDate().getTime();
			beginDateInMilis = beginDateInMilis - ((Task) refItem.getBean()).getBeginDate().getTime();
			beginDateInMilis = beginDateInMilis /TimeConstants.DAY_IN_MILLIS * ZoomConstants.TIMELINE_COLUMN_WIDTH * ZoomConstants.FACTOR;
			targetItem.setXCoordinate(beginDateInMilis.intValue());
		}
		
	}

	/**
	 * Adds a {@link org.tbjd.client.guis.gantt.items.GanttResourceItem GanttResourceItem} to the given
	 * {@link org.tbjd.client.guis.gantt.items.GanttTaskItem GanttTaskItem}
	 * 
	 * @param taskItem
	 * @param resource
	 */
	private void addResourceItem(GanttTaskItem taskItem, Resource resource) {
		// add to the Resource a new taskId
		resource.addTask(((Task) taskItem.getBean()));

		// and add a GanttResourceItem to the GanttTaskItem
		for (AbstractGanttItem eachItem : this.lstGanttItems) {
			if (eachItem.equals(taskItem)) {
				((GanttTaskItem) eachItem).addResource(resource);
			}
		}
	}

	/**
	 * <p>
	 * Remove the {@link org.tbjd.client.beans.Resource resource} from the list of Resources
	 * </p>
	 * 
	 * @param resource the Resource to remove
	 */
	private void removeResource(Resource resource) {
		if (this.resources == null){
			this.resources = new ArrayList<Resource>();
		}
		for (Resource eachResource : this.resources) {
			if (eachResource.getId() == resource.getId()) {
				System.out.println("GanttItemContainerGui.replaceResource() found " + resource.getName());
				// TODO remove the founded resource (the below instruction is false)
				eachResource = resource;
			}
		}
	}

	/**
	 * This method updates the date of the dragged item.
	 * 
	 * @param action
	 *            the map containing useful informations to process
	 */
	private void updateDateOfDraggedItem(Object action) {
		// FIXME issues when dragging on border and dropping quickly
		Date newDate = null;

		if (((Map<?, ?>) action).containsKey(NotificationsConstants.DRAGGING_DRAGGABLEGANTTITEM)) {
			System.out.println("GanttItemContainerGui.updateDateOfDraggedItem() DRAGGING_DRAGGABLEGANTTITEM");
			int offset = (Integer) ((Map<Integer, ?>) action).get(NotificationsConstants.DRAGGING_OFFSET) + this.offsetMultiple;
			if (offset > 0) {
				offset += ZoomConstants.TIMELINE_COLUMN_WIDTH * ZoomConstants.FACTOR / 2;
			} else {
				offset -= ZoomConstants.TIMELINE_COLUMN_WIDTH * ZoomConstants.FACTOR / 2;
			}
			int multiple = (offset) / (ZoomConstants.TIMELINE_COLUMN_WIDTH * ZoomConstants.FACTOR / 2) / 2;
			newDate = new Date(this.draggedItemRefBeginDate.getTime() + multiple * TimeConstants.DAY_IN_MILLIS);
		}

		if (((Map<?, ?>) action).containsKey(NotificationsConstants.DRAGGING_GANTTITEM_ONBORDER)) {
			System.out.println("GanttItemContainerGui.updateDateOfDraggedItem() DRAGGING_GANTTITEM_ONBORDER");
			int multiple = (Integer) ((Map<Integer, ?>) action).get(NotificationsConstants.DRAGGING_GANTTITEM_ONBORDER);
			newDate = new Date(((Task) this.draggedItem.getBean()).getBeginDate().getTime() + multiple * TimeConstants.DAY_IN_MILLIS);
			this.offsetMultiple += multiple * ZoomConstants.TIMELINE_COLUMN_WIDTH * ZoomConstants.FACTOR;
		}

		// change date of item then beans...
		((AbstractGanttItem) this.draggedItem).setLabel(DateTimeFormat.getFormat("dd.MM.yy").format(newDate));
		((Task) this.draggedItem.getBean()).setBeginDate(newDate);
	}

	public void executeBorderDetected(Widget draggable, int directionX, int directionY) {
		// move children in every case (dragging or hovering)
		moveChildren(draggable, directionX, directionY);

		// if we're currently dragging a AbstractGanttItem towards a border
		if (draggable != null) {

			// update the date of the dragged AbstractGanttItem
			this.map = new HashMap<Integer, Object>();
			this.map.put(NotificationsConstants.DRAGGING_GANTTITEM_ONBORDER, new Integer(directionX * -1));
			updateDateOfDraggedItem(this.map);
		}
	}

	/**
	 * <p>
	 * Moves every {@link org.tbjd.client.guis.gantt.items.AbstractGanttItem AbstractGanttItem} except the one being dragged if there's one
	 * </p>
	 * <p>
	 * This method is used when a border hovering/dragging is detected
	 * </p>
	 * 
	 * @param draggable
	 *            the object being dragged if there's one or null
	 * @param directionX
	 *            the direction on the 'x' axis toward which the DraggableGanttItem will move
	 * @param directionY
	 *            the direction on the 'y' axis toward which the DraggableGanttItem will move
	 * @see org.tbjd.client.interfaces.BorderListener
	 */
	private void moveChildren(Widget draggable, int directionX, int directionY) {
		long draggedItemId = -1;

		if (draggable != null) {
			draggedItemId = ((AbstractGanttItem) draggable).getId();
		}

		// for every AbstractGanttItem
		for (AbstractGanttItem eachGanttItem : this.lstGanttItems) {

			// if the current draggable's id is different from the draggedItem's id
			if (eachGanttItem.getId() != draggedItemId) {
				// I like to move it, move it ... huh baby !
				this.container.setWidgetPosition((Widget) eachGanttItem, this.container.getWidgetLeft((Widget) eachGanttItem) + directionX
						* ZoomConstants.TIMELINE_COLUMN_WIDTH * ZoomConstants.FACTOR, this.container.getWidgetTop((Widget) eachGanttItem)
						+ directionY * ZoomConstants.TIMELINE_COLUMN_WIDTH * ZoomConstants.FACTOR);
			}
		}
	}

	public void setDragController(DragController dragController) {
		if (dragController instanceof GanttItemDragController) {
			this.ganttItemDragController = (GanttItemDragController) dragController;
			this.dropController = new GridConstrainedDropController(this.container, ZoomConstants.TIMELINE_COLUMN_WIDTH * ZoomConstants.FACTOR,
					GanttConstants.DRAGGABLEGANTTITEM_HEIGHT);
			this.ganttItemDragController.registerDropController(this.dropController);
			// beware that changing proxy mode will change the ganttItemDragController.getParent() !
			// this.dragController.setBehaviorDragProxy(false);
		}

		if (dragController instanceof ResourcePickerDragController) {
			this.resourcePickerDragController = (ResourcePickerDragController) dragController;
		}
	}

	public AbsolutePanel getContainer() {
		return container;
	}

	public void addBorderNotifier() {
		this.borderNotifier = GanttBorderNotifier.getInstance();
	}

	public void defineTrigger() {
	}

	public void update(Object sender) {
		if (((Map<?, ?>) sender).containsKey(NotificationsConstants.GANTTITEMS_DECORATED)) {
			System.out.println("GanttItemContainerGui.update() GANTTITEMS_DECORATED");

			// update the list of Resources and Tasks
			this.tasks = (List<Task>) ((Map<Integer, ?>) sender).get(NotificationsConstants.TASKS);
			this.resources = (List<Resource>) ((Map<Integer, ?>) sender).get(NotificationsConstants.RESOURCES);

			// unregister each resourceDropController with the resourcePickerDragController before setting the new list
			for (ResourcePickerDropController controller : this.lstResourcePickerDropController) {
				this.resourcePickerDragController.unregisterDropController(controller);
			}

			// update the list of AbstractGanttItems field
			this.lstGanttItems = (List<AbstractGanttItem>) ((Map<Integer, ?>) sender).get(NotificationsConstants.GANTTITEMS_DECORATED);
			
			display();
		}
	}

	public void display(List<?> updatedBeans) {
		// TODO Auto-generated method stub
		
	}

}
